package ua.edu.nuos.jeetraining2012.cms.model;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ua.edu.nuos.jeetraining2012.cms.entity.UserEntity;
import ua.edu.nuos.jeetraining2012.cms.model.exception.DAOException;

import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.Arrays;
import java.util.List;

/**
 * DAO class to proceed with user table.
 * <p/>
 * Available actions:
 * <ul>
 * <li>Find user: {@link UserDAO#find(ua.edu.nuos.jeetraining2012.cms.entity.UserEntity)}</li>
 * <li>Create user: {@link UserDAO#create(ua.edu.nuos.jeetraining2012.cms.entity.UserEntity)}</li>
 * <li>Update user: {@link UserDAO#update(ua.edu.nuos.jeetraining2012.cms.entity.UserEntity)}</li>
 * <li>Delete user: {@link UserDAO#delete(ua.edu.nuos.jeetraining2012.cms.entity.UserEntity)}</li>
 * </ul>
 * All method accept UserEntity object.
 *
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 23.04.13 Time: 22:21
 * @since 1.0
 */
@Local
@Stateless
public class UserDAO {

    /**
     * Define logger
     */
    final private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * Define EntityManager.
     * All transaction management operations will be controlled by AP server.
     */
    @PersistenceContext
    private EntityManager em = null;

    // Getters & Setter for tests.
    private void setEm(EntityManager em) {
        this.em = em;
    }

    protected EntityManager getEm() {
        return em;
    }

// Constructors

    /**
     * Default constructor.
     */
    public UserDAO() {
        logger.debug("UserDAO - start.");
    }

    /**
     * Constructor for tests, can initialize EntityManager.
     *
     * @param em Instance of EntityManager object.
     */
    public UserDAO(EntityManager em) {
        logger.debug("UserDAO - start with entity manager {}.", em);
        setEm(em);
    }

    /**
     * Create new user record.
     * <p/>
     * You should send filled in UserEntity object. In case of success, this method MUST return
     * id of inserted object.
     *
     * @param dto Instance of UserEntity.
     * @return Id of inserted bean.
     * @throws DAOException             in cases, when can't add user.
     * @throws IllegalArgumentException in case when Entity is null.
     */
    public String create(UserEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("create() - start.");

        if (dto == null) {
            logger.warn("NULL UserEntity");
            logger.debug("create() - finish with NPE.");
            throw new IllegalArgumentException("Can't persists null valued entity");
        }

        String userInsertId = null;
        logger.debug("Adding entity {} to DB", dto);
        try {
            em.persist(dto);
            em.flush();
            userInsertId = dto.getId();
            logger.debug("Values after insert: {} into DB.", dto);
        } catch (Exception e) {
            logger.warn("Error! Can't add user.\nMessage: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("create() - finish with DAOException.");
            throw new DAOException("Unable to add user. Error message: " + e.getMessage());
        }

        logger.debug("create() - finish.");
        return userInsertId;
    }

    /**
     * Find user.
     * <p/>
     * To find user send instance of UserEntity object with filled id. Any other fields are
     * unnecessary.
     * In case of success, instance of UserEntity will be returned, in other cases -- null.
     *
     * @param dto Instance of UserEntity with filled id.
     * @return Return instance of UserEntity or null, if object was not found.
     * @throws DAOException             in case, when parent exceptions appeared.
     * @throws IllegalArgumentException in case, when Entity is null.
     */
    public UserEntity find(UserEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("find() - start.");

        if (dto == null) {
            logger.warn("NULL UserEntity");
            logger.debug("find() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't find null valued entity.");
        }

        UserEntity user = null;

        logger.debug("Trying to find user with {} value", dto);
        try {
            user = em.find(UserEntity.class, dto.getId());
            logger.debug("Founded user data: {}", user);
        } catch (Exception e) {
            logger.warn("Error! Can't retrieve user. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("find() - finished with DAOException");
            throw new DAOException("Unable to find user. Error message: " + e.getMessage());
        }

        logger.debug("find() - finish.");
        return user;
    }


    /**
     * Find user.
     * <p/>
     * Find user by email.
     * In case of success, instance of UserEntity will be returned, in other cases -- null.
     *
     * @param email Email address to be searched.
     * @return Return instance of UserEntity or null, if object was not found.
     * @throws DAOException             in case, when parent exceptions appeared.
     * @throws IllegalArgumentException in case, when Entity is null.
     */
    public UserEntity findByEmail(String email) throws DAOException, IllegalArgumentException {
        logger.debug("find() - start.");

        if (email == null) {
            logger.warn("NULL Email");
            logger.debug("find() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't find null valued entity.");
        }

        UserEntity user = null;

        logger.debug("Trying to find user with \"{}\" value", email);
        try {
            user = (UserEntity) em.createQuery("SELECT u FROM UserEntity as u WHERE u.email = :email").setParameter("email",email).getSingleResult();
            logger.debug("Founded user data: {}", user);
        }  catch (NoResultException e) {
            logger.debug("find() - user not found");
            return null;
        } catch (Exception e) {
            logger.warn("Error! Can't retrieve user. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("find() - finished with DAOException");
            throw new DAOException("Unable to find user. Error message: " + e.getMessage());
        }

        logger.debug("find() - finish.");
        return user;
    }

    /**
     * Update User.
     * <p/>
     * Update user with send UserEntity. To update user, please set in this bean its id
     * and changed fields. After that just send those DTO to this method.
     * In case of success, true will be returned.
     *
     * @param dto Instance of changed UserEntity
     * @return True in case of success, either return false.
     * @throws DAOException             in case, when can't update user or parent exception appeared.
     * @throws IllegalArgumentException in case, when Entity is null.
     */
    public boolean update(UserEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("update() - start.");
        boolean isUpdated = false;

        if (dto == null) {
            logger.warn("NULL UserEntity");
            logger.debug("update() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't update with null valued Entity.");
        }

        logger.debug("Updating user with data: {}", dto);
        try {
            em.merge(dto);
            isUpdated = true;
            logger.debug("User updated success.");
        } catch (Exception e) {
            logger.warn("Error! Can't update user. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("update() - finished with DAOException.");
            throw new DAOException("Unable to update user. Error message: " + e.getMessage());
        }
        logger.debug("update() - finish.");
        return isUpdated;
    }

    /**
     * Delete User.
     * <p/>
     * Delete existent user from DB. You have to send DTO with filled in user id.
     * In case of success, true will be returned.
     *
     * @param dto Instance of UserEntity object with filled in id.
     * @return True in case of success. Either -- false.
     * @throws DAOException             in case, when can't delete user or parent exceptions appeared.
     * @throws IllegalArgumentException in case, when Entity is null.
     */
    public boolean delete(UserEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("delete() - start.");

        if (dto == null) {
            logger.warn("NULL UserEntity");
            logger.debug("delete() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't delete with null valued Entity.");
        }

        boolean isDeleted = false;
        logger.debug("Deleting user with data: {}", dto);
        try {
            UserEntity user = em.find(UserEntity.class, dto.getId());
            em.remove(user);
            isDeleted = true;
            logger.debug("User success deleted.");
        } catch (Exception e) {
            logger.warn("Error! Can't delete user. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("delete() - finished with DAOException.");
            throw new DAOException("Unable to delete user. Error message: " + e.getMessage());
        }

        logger.debug("delete() - finish.");
        return isDeleted;
    }

    /**
     * Find by id.
     * <p/>
     * Find user by its id. In case of success, UserEntity object will be returned, either null.
     * In case, when find command will throw exception. DAOException will be thrown with parent exception message.
     * In case, when id is null, IllegalArgumentException will be thrown.
     *
     * @param id String identifier of user, i.e. user_id.
     * @return UserEntity object or null.
     * @throws DAOException             In cases, when can't find user or any parent exception appeared.
     * @throws IllegalArgumentException In case, when id is null.
     */
    public UserEntity findById(String id) throws DAOException, IllegalArgumentException {
        logger.debug("findByUserId() - start()");
        if (id == null) {
            logger.warn("Null id");
            logger.debug("findByUserId() - finished with IllegalArgumentException");
            throw new IllegalArgumentException("Can't find with null id.");
        }

        UserEntity user = null;
        try {
            user = em.find(UserEntity.class, id);
        } catch (Exception e) {
            logger.warn("Error!. Can't find user. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("findByUserId() - finished with DAOException");
            throw new DAOException("Unable to find user. Error message: " + e.getMessage());
        }

        logger.debug("findByUserId() - finished.");
        return user;
    }


    /**
     * Find all users.
     * <p/>
     * Use this method to find out all available users.
     * In case of success, List of UserEntity objects will be returned.
     * In some other cases (when parent exception caught), DAOException can be produced.
     *
     * @return List of users.
     * @throws DAOException Thrown in cases, when parent exception was thrown.
     */
//    @SuppressWarnings("unchecked")
    public List<UserEntity> findAll() throws DAOException {
        logger.debug("Method findAll() invocation.");
        List<UserEntity> users;

        try {
            Query q = em.createQuery("SELECT c from UserEntity c");
            logger.debug("Trying to findAll with query: {}", q.toString());

            users = q.getResultList();
            logger.debug("Fetching result... Get {} items in list.", users.size());

        } catch (IllegalArgumentException e) {
            logger.warn("Query string is invalid. Error message: {} \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("findAll() - finished with DAOException.");
            throw new DAOException("Unable to find all Users. Error message: " + e.getMessage());
        } catch (Exception e) {
            logger.warn("Error, parent exception exceeded. Message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            throw new DAOException("Unable to find all users. Error message: " + e.getMessage());
        }

        logger.debug("findAll() - finished.");
        return users;
    }

    /**
     * This method computes checksum for user with specified email.
     * This checksum can be further used in activation link
     *
     * @param email User email
     * @return      md5 hash result of concatenation id and email fields
     * @throws IllegalArgumentException When email is null
     * @throws DAOException             When persistence exception occurred
     */
    public String approveChecksum(String email) throws IllegalArgumentException, DAOException {
        logger.debug("approveChecksum() - start.");

        if (email == null) {
            logger.warn("Null email");
            logger.debug("approveChecksum() - finished with IllegalArgumentException");
            throw new IllegalArgumentException("Can't calc md5 for object with null id.");
        }

        String sql = "SELECT md5(\"id\" || \"email\") AS \"user_checksum\" " +
                     "FROM \"users\" WHERE email = ?";
        String md5 = null;

        try {
            Query query = em.createNativeQuery(sql).setParameter(1, email);
            logger.debug("approveChecksum() - finding result by Query: " + query.toString());

            md5 = query.getSingleResult().toString();
            logger.debug("Result is:" + md5);
        } catch (NoResultException e) {
            logger.debug("No result was found. Message: " + e.getMessage());
        } catch (Exception e) {
            logger.warn("Error! Can't get checksum. Message: " + e.getMessage() + "\nTrace\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("approveChecksum() - finished with DAOException");
            throw new DAOException("Unable to calc checksum. Error message: " + e.getMessage());
        }
        logger.debug("approveChecksum() - finished.");

        return md5;
    }

}
